#include "OgreGLES2Texture.h"
#include "OgreGLES2PixelFormat.h"
#include "OgreGLES2RenderSystem.h"
#include "OgreGLES2HardwarePixelBuffer.h"
#include "OgreRoot.h"

namespace Ogre
{
	static inline void doImageIO(const String& name, const String& group,
								 const String& ext,
								 vector<Image>::type& images,
								 Resource* r)
	{
		size_t imgIdx = images.size();
		images.push_back(Image());
		
		DataStreamPtr dstream = 
			ResourceGroupManager::getSingleton().openResource(
				name, group, true, r);
				
		images[imgIdx].load(dstream, ext);
	}
	
	GLES2Texture::GLES2Texture(ResourceManager* creator, const String& name,
							  ResourceHandle handle, const String& group, bool isManual,
							  ManualResourceLoader* loader, GLES2Support& support)
		: Texture(creator, name, handle, group, isManual, loader),
		  mTextureID(0), mGLSupport(support)
	{
	}
	
	GLES2Texture::~GLES2Texture()
	{
		if (isLoaded())
		{
			unload();
		}
		else
		{
			freeInternalResources();
		}
	}
	
	GLenum GLES2Texture::getGLES2TextureTarget(void) const
	{
		switch(mTextureType)
		{
			case TEX_TYPE_1D:
			case TEX_TYPE_2D:
				return GL_TEXTURE_2D;
			case TEX_TYPE_CUBE_MAP:
				return GL_TEXTURE_CUBE_MAP;
			default:
				return 0;
		};
	}
	
	void GLES2Texture::createInternalResourcesImpl(void)
	{
		mWidth = GLES2PixelUtil::optionalPO2(mWidth);
		mHeight = GLES2PixelUtil::optionalPO2(mHeight);
		mDepth = GLES2PixelUtil::optionalPO2(mDepth);
		
		mFormat = TextureManager::getSingleton().getNativeFormat(mTextureType, mFormat, mUsage);
		
		size_t maxMips = GLES2PixelUtil::getMaxMipmaps(mWidth, mHeight, mDepth, mFormat);
		
		if (PixelUtil::isCompressed(mFormat) && (mNumMipmaps == 0))
			mNumRequestedMipmaps = 0;
		
		mNumMipmaps = mNumRequestedMipmaps;
		if (mNumMipmaps > maxMips)
			mNumMipmaps = maxMips;
			
		glGenTextures(1, &mTextureID);
		GL_CHECK_ERROR;
		
		glBindTexture(getGLES2TextureTarget(), mTextureID);
		GL_CHECK_ERROR;
		
#if GL_APPLE_texture_max_level
		glTexParameteri(getGLES2TextureTarget(), GL_TEXTURE_MAX_LEVEL_APPLE, mNumMipmaps);
#endif		
		
		glTexParameteri(getGLES2TextureTarget(),
						GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		GL_CHECK_ERROR;
		glTexParameteri(getGLES2TextureTarget(),
						GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		GL_CHECK_ERROR;
		glTexParameteri(getGLES2TextureTarget(),
						GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		GL_CHECK_ERROR;
		glTexParameteri(getGLES2TextureTarget(),
						GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		GL_ECHECK_ERROR;
		
		mMipmapsHardwareGenerated = 
			Root::getSingleton().getRenderSystem()->getCapabilities()->hasCapability(RSC_AUTOMIPMAP) && !PixelUtil::isCompressed(mFormat);
		
		if ((mUsage & TU_AUTOMIPMAP) &&
			mNumRequestedMipmaps && mMipmapsHardwareGenerated)
		{
			glGenerateMipmap(getGLES2TextureTarget());
			GL_CHECK_ERROR;
		}
		
		GLenum format = GLES2PixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma);
		GLenum datatype = GLES2PixelUtil::getGLOriginDataType(mFormat);
		size_t width = mWidth;
		size_t height = mHeight;
		size_t depth = mDepth;
		
		if (PixelUtil::isCompressed(mFormat))
		{
			size_t size = PixelUtil::getMemorySize(mWidth, mHeight, mDepth, mFormat);
			uint8* tmpdata = OGRE_NEW_FIX_FOR_WIN32 uint8[size];
			memset(tmpdata, 0, size);
			for (size_t mip = 0; mip <= mNumMipmaps; mip++)
			{
				size = PixelUtil::getMemorySize(width, height, depth, mFormat);
				
				switch(mTextureType)
				{
					case TEX_TYPE_1D:
					case TEX_TYPE_2D:
						glCompressedTexImage2D(GL_TEXTURE_2D,
											   mip,
											   format,
											   width, height,
											   0, 
											   size,
											   tmpdata);
						GL_CHECK_ERROR;
						break;
					case TEX_TYPE_CUBE_MAP:
						for (int face = 0; face < 6; face++)
						{
							glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, format,
									width, height, 0
									size, tmpdata);
							GL_CHECK_ERROR;
						}
						break;
					case TEX_TYPE_3D:
					default:
						break;
				};
				if (width > 1)
				{
					width = width / 2;
				}
				if (height > 1)
				{
					height = height / 2;
				}
				if (depth > 1)
				{
					depth = depth / 2;
				}
			}
			OGRE_DELETE[] tmpdata;
		}
		else
		{
			for (size_t mip = 0; mip <= mNumMipmaps; mip++)
			{
				switch(mTextureType)
				{
				case TEX_TYPE_1D:
				case TEX_TYPE_2D:
					glTexImage2D(GL_TEXTURE_2D,
								 mip,
								 format,
								 width, height,
								 0, 
								 format,
								 datatype, 0);
					GL_CHECK_ERROR;
					break;
				case TEX_TYPE_CUBE_MAP:
					for (int face = 0; face < 6; face++)
					{
						glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mip, format,
							width, height, 0,
							format, datatype, 0);
					}
					break;
				case TEX_TYPE_3D:
				default:
					break;
				};

				if (width > 1)
				{
					width = width / 2;
				}
				if (height > 1)
				{
					height = height / 2;
				}
			}
		}

		_createSurfaceList();

		mFormat = getBuffer(0, 0)->getFormat();
	}

	void GLES2Texture::createRenderTexture(void)
	{
		createInternalResources();
	}

	void GLES2Texture::prepareImpl()
	{
		if (mUsage & TU_RENDERTARGET)
			return;

		String baseName, ext;
		size_t pos = mName.find_last_of(".");
		baseName = mName.substr(0, pos);

		if (pos != String::npos)
		{
			ext = mName.substr(pos + 1);
		}

		LoadedImages loadedImages = LoadedImages(OGRE_NEW_FIX_FOR_WIN32 vector<Image>::type());

		if (mTextureType == TEX_TYPE_1D || mTextureType == TEX_TYPE_2D)
		{
			doImageIO(mName, mGroup, ext, *loadedImages, this);

			if ((*loadedImages)[0].hasFlag(IF_CUBEMAP))
				mTextureType = TEX_TYPE_CUBE_MAP;

			PixelFormat imageFormat = (*loadedImages)[0].getFormat();
			if (imageFormat == PF_PVRTC_RGB2 || imageFormat == PF_PVRTC_RGBA2 ||
				imageFormat == PF_PVRTC_RGB4 || imageFormat == PF_PVRTC_RGBA4)
			{
				size_t imageMips = (*loadedImages)[0].getNumMipmaps();
				if (imageMips == 0)
				{
					mNumMipmaps = mNumRequestedMipmaps = imageMips;
					mUsage &= ~TU_AUTOMIPMAP;
				}
			}
		}
		else if (mTextureType == TEX_TYPE_CUBE_MAP)
		{
			if (getSourceFileType() == "dds")
			{
				doImageIO(mName, mGroup, ext, *loadedImages, this);
			}
			else
			{
				vector<Image>::type images(6);
				ConstImageList imagePtrs;
				static const String suffixes[6] = {"_rt", "_lf", "_up", "_dn", "_fr", "_bk"};

				for (size_t i = 0; i < 6; i++)
				{
					String fullName = baseName + suffixes[i];
					if (!ext.empty())
						fullName += fullName + "." + ext;
					doImageIO(fullName, mGroup, ext, *loadedImages, this);
				}
			}
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_NOT_IMPLEMENTED,
						"**** Unknown texture type ****",
						"GLES2Texture::prepare");
		}

		mLoadedImages = loadedImages;
	}

	void GLES2Texture::unprepareImpl()
	{
		mLoadedImages.setNull();
	}

	void GLES2Texture::loadImpl()
	{
		if (mUsage & TU_RENDERTARGET)
		{
			createRenderTexture();
			return;
		}

		LoadedImages loadedImages = mLoadedImages;
		mLoadedImages.setNull();

		ConstImagePtrList imagePtrs;
		for (size_t i = 0; i < loadedImages->size(); ++i)
		{
			imagePtrs.push_back(&(*loadedImages)[i]);
		}

		_loadImages(imagePtrs);
	}

	void GLES2Texture::freeInternalResourcesImpl()
	{
		mSurfaceList.clear();
		glDeleteTextures(1, &mTextureID);
		GL_CHECK_ERROR;
	}

	void GLES2Texture::_createSurfaceList()
	{
		mSurfaceList.clear();

		bool wantGenerateMips = (mUsage & TU_AUTOMIPMAP) != 0;

		bool doSoftware = wantGenerateMips && !mMipmapsHardwareGenerated && getNumMipmaps();

		for (size_t face = 0; face < getNumFaces(); face++)
		{
			size_t width = mWidth;
			size_t height = mHeight;

			for (size_t mip = 0; mip <= getNumMipmaps(); mip++)
			{
				GLES2HardwarePixelBuffer* buf = OGRE_NEW GLES2TextureBuffer(mName,
																			getGLES2TextureTarget(),
																			mTextureID,
																			width, height,
																			GLES2PixelUtil::getClosestGLInternalFormat(mFormat, mHwGamma),
																			GLES2PixelUtil::getGLOriginDataType(mFormat),
																			face,
																			mip,
																			static_cast<HardwareBuffer::Usage>(mUsage),
																			doSoftware && mip == 0, mHwGamma, mFSAA);

				mSurfaceList.push_back(HardwarePixelBufferSharedPtr(buf));

				if (mFormat == PF_PVRTC_RGB2 || mFormat == PF_PVRTC_RGBA2 ||
					mFormat == PF_PVRTC_RGB4 || mFormat == PF_PVRTC_RGBA4)
				{
					if (width > 1)
					{
						width = width / 2;
					}
					if (height > 1)
					{
						height = height / 2;
					}
				}

				if (buf->getWidth() == 0 ||
					buf->getHeight() == 0 ||
					buf->getDepth() == 0)
				{
					OGRE_EXCEPT(
                        Exception::ERR_RENDERINGAPI_ERROR,
                        "Zero sized texture surface on texture "+getName()+
                            " face "+StringConverter::toString(face)+
                            " mipmap "+StringConverter::toString(mip)+
                            ". The GL probably driver refused to create the texture.",
                            "GLES2Texture::_createSurfaceList");
				}
			}
		}
	}

	HardwarePixelBufferSharedPtr GLES2Texture::getBuffer(size_t face, size_t mipmap)
	{
		if (face >= getNumFaces())
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
						"Face index out of range",
						"GLES2Texture::getBuffer");
		}

		if (mipmap > mNumMipmaps)
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
						"Mipmap index out of range",
						"GLES2Texture::getBuffer");
		}

		unsigned int idx = face * (mNumMipmaps + 1) + mipmap;
		assert(idx < mSurfaceList.size());
		return mSurfaceList[idx];
	}
}
